﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

/* 使用 Process 和 CancellationToken 来实现异步线程任务的取消。
 * 
 * ProcessCancellationTask 类中，通过使用 CancellationTokenSource 创建一个可以自动取消的任务。
 * 
 * FooAsync 方法模拟了一个长时间执行的非常见阻塞的耗时操作，用于演示如何将这种类型的操作取消。
 * 
 * 
 * CancelableProcessTask 类封装了一个可取消的线程任务。
 * 
 * 该任务可以通过 CancellationToken 在外部请求取消。
 * 
 * 调用 token.Register() 注册的回调用于中止线程任务，并设置任务状态为取消。
 */
namespace Demo
{
    public class ProcessCancellationTask
    {
        /// <summary>
        /// 异步方法 Fun，用于演示线程的取消任务
        /// </summary>
        public async void Fun()
        {
            // 创建一个3秒后自动取消的CancellationTokenSource
            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));

            // 创建一个可取消的线程任务，并传递需要执行的操作FooAsync
            var threadTask = new CancelableThreadTask(() => FooAsync());

            // 使用SimpleTimer来记录任务开始和执行时间
            using (new SimpleTimer("Task started"))
            {
                try
                {
                    // 启动线程任务，并传递取消令牌
                    var task = threadTask.RunAsync(cts.Token);

                    // 等待任务完成
                    await task;
                }
                catch (OperationCanceledException)
                {
                    // 捕获任务取消异常，并打印取消信息
                    Console.WriteLine("Task was canceled");
                }
            }
        }

        /// <summary>
        /// 模拟的任务方法 FooAsync
        /// </summary>
        void FooAsync()
        {
            try
            {
                // 打印任务开始信息
                Console.WriteLine("Foo start...");

                // 模拟耗时操作，等待5秒，这种阻塞无法使用 CancelableProcessTask 取消
                //await Task.Delay(5000);

                // 模拟耗时操作，等待5秒，这种阻塞可以使用 CancelableProcessTask 取消
                //Thread.Sleep(5000);

                // 模拟耗时操作，耗时6秒，这种阻塞可以使用 CancelableProcessTask 的 Abort 取消，Interrupt不能取消
                long sum = 0;
                for (long i = 0; i < 3_000_000_000; i++)
                {
                    sum++;
                }

                // 打印任务结束信息
                Console.WriteLine("Foo end...");
            }
            catch (Exception)
            {
                // 捕获任何异常并打印任务取消信息
                Console.WriteLine("Foo cancelled...");
            }
        }
    }

    public class CancelableProcessTask
    {
        /// <summary>
        /// 保存要启动的进程文件名和参数
        /// </summary>
        private readonly string _filename;
        private readonly string _arguments;

        /// <summary>
        /// 进程实例
        /// </summary>
        private Process _process;
        /// <summary>
        /// 用于表示任务完成状态的 TaskCompletionSource
        /// </summary>
        private TaskCompletionSource<object> _tcs;

        /// <summary>
        /// 标识任务是否正在运行
        /// </summary>
        private int _isRunning = 0;

        /// <summary>
        /// 构造函数，初始化要执行的进程文件名和参数
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="arguments"></param>
        public CancelableProcessTask(string filename, string arguments)
        {
            _filename = filename;
            _arguments = arguments;
        }

        /// <summary>
        /// 启动进程并异步运行任务
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public Task RunAsync(CancellationToken token)
        {
            // 用于确保同一实例中的任务不会被多次启动，避免竞争条件。
            if (Interlocked.CompareExchange(ref _isRunning, 1, 0) == 1)
                throw new InvalidOperationException("Task is already running");

            // 初始化任务完成标识
            _tcs = new TaskCompletionSource<object>();

            // 创建进程实例，配置启动信息
            _process = new Process
            {
                // 定义了启动进程的详细信息，包括可执行文件名、参数、是否创建窗口等。
                StartInfo = new ProcessStartInfo
                {
                    // 要执行的可执行文件名
                    FileName = _filename,
                    // 传递给进程的参数
                    Arguments = _arguments,
                    // 不使用操作系统的shell启动
                    UseShellExecute = false,
                    // 重定向标准输出
                    RedirectStandardOutput = true,
                    // 重定向标准错误输出
                    RedirectStandardError = true,
                    // 不创建窗口
                    CreateNoWindow = true
                }
            };

            // 启动进程并进入执行
            _process.Start();

            // 启用进程退出事件通知
            _process.EnableRaisingEvents = true;

            // 当进程退出时执行
            _process.Exited += (sender, args) =>
            {
                if (_process.ExitCode == 0)
                    // 进程正常退出，任务完成
                    _tcs.SetResult(null);
                else
                {
                    if (token.IsCancellationRequested)
                        // 如果请求取消，则设置任务为取消状态
                        _tcs.TrySetCanceled(token);
                    else
                        // 否则设置任务异常状态
                        _tcs.SetException(new Exception($"Process exited with code {_process.ExitCode}"));
                }
            };

            // 注册取消令牌的回调函数
            token.Register(() =>
            {
                // 如果成功终止进程，则任务会设置为取消状态
                if (Interlocked.CompareExchange(ref _isRunning, 0, 1) == 1)
                {
                    // 如果任务正在运行，并接收到取消任务请求，则终止进程
                    _process.Kill();
                }
            });

            // 返回表示任务的 Task
            return _tcs.Task;
        }
    }
}
